'''
@author：fc
@date：  2021/11/30
@contact：675435108@qq.com
'''
import math
import os
import random

import numpy as np
import json

"""
文件内容&功能简要：
# 实现基于内容的推荐系统：
# 方法：1.计算item的特征信息矩阵
       2.构建用户的偏好矩阵
       3.计算用户和item的余弦相似度
# 数据集： ml-1l
# 参考：推荐系统开发实战-高阳团-5.3（编写一个基于内容推荐算法的电影推荐系统）
"""
import pandas as pd


class BaseContentRec:
    def __init__(self,K):
        self.K=K
        self.item_profile = self.prepare_item_profile()
        self.user_profile = self.prepare_user_profile()

    """
       1.计算item（电影）的特征信息矩阵
    """
    def prepare_item_profile(self,file="./data/generate/ml-1m/movies.csv"):
        items = pd.read_csv(file)
        item_ids = set(items["movieId"].values)
        self.item_dict = {}  # key为movieId,value为[电影类型1,电影类型2,电影类型3]
        genres_all = list()  # 存放所有电影类型
        for item in item_ids:
            # 找到movies.csv中movieId=id的所有行坐标对应的电影类型（genres）中的第一个，然后切割成list
            genres = items[items["movieId"]==item]["genres"].values[0].split("|")
            self.item_dict.setdefault(item,[]).extend(genres) # 取出genres中的值并放置到每个id对应的[]中去
            genres_all.extend(genres)  # 里面的电影类型有重复
        self.genres_all = set(genres_all) # 去除genres里面有重复的电影类型
        with open("./reml-1l/contentBaseRec/genres.txt", 'w') as fh:
            fh.write(str(list(set(genres_all))))
        self.item_matrix = {} # key为movieId,value为[0,1,x,x,x,....,x]独热编码
        for item in self.item_dict.keys():
            self.item_matrix[str(item)] = [0]*len(set(self.genres_all))
            for genre in self.item_dict[item]:
                index = list(set(genres_all)).index(genre)
                self.item_matrix[str(item)][index]=1
        return  self.item_matrix
        # if os.path.exists("./reml-1l/contentBaseRec/item_profile.json"):
        #     return json.load(open("./reml-1l/contentBaseRec/item_profile.json",'r'))
        # else:
        #     json.dump(self.item_matrix, open("./reml-1l/contentBaseRec/item_profile.json", "w"))
        #
        #     print("item特征信息矩阵计算完成，保存在{}".format("./reml-1l/comtentBaseRec/item_profile.json"))

            
    """
      2.计算用户对（电影类型）的偏好程度
    """
    def prepare_user_profile(self,file="./data/generate/ml-1m/ratings.csv"):
        users = pd.read_csv(file)
        user_ids = set(users["userId"].values)

        """
           转换users为字典：key:userId,value为{key为itemId：value为评分}
        """
        users_rating_dict ={}
        for user in user_ids:
            users_rating_dict.setdefault(str(user),{})
        with open(file,'r') as fr:
            for line in fr.readlines():
                if not line.startswith("userId"):
                    (user,item,rate)  = line.split(",")[:3]
                    users_rating_dict[user][item] = int(rate)
        # 转换结束
        """
          获取每个用户对每个类型下的哪些电影进行了评分
        """
        self.user_matrix ={}
        for user in users_rating_dict.keys():
            print(f"user is {user}")
            score_list=users_rating_dict[user].values() # 该用户对电影的评分
            avg = sum(score_list)/len(score_list) # 计算平均分
            self.user_matrix[user]=[]
            # 遍历所有电影类型（保证item_profile和user_profile中每列表示的电影类型一致）
            for genre in self.genres_all:
                score_all = 0.0
                score_len = 0
                for item in users_rating_dict[user].keys(): # itemId为用户看过的电影
                    # 判断电影类型是否在用户评分过的电影里
                    if genre in self.item_dict[int(item)]:
                        score_all += (users_rating_dict[user][item]-avg)
                        score_len += 1
                if score_len==0:
                    self.user_matrix[user].append(0.0)
                else:
                    self.user_matrix[user].append(score_all/score_len)
        return self.user_matrix
        #json.dump(self.user_matrix, open("./reml-1l/contentBaseRec/user_profile.json", 'w'))
        # print("user信息计算完成，保存路径为：{}".format("./reml-1l/contentBaseRec/user_profile.json"))
    # 获取用户对未进行评分的item列表
    def get_none_score_item(self,user):
        items = pd.read_csv("./data/generate/ml-1m/movies.csv")['movieId'].values
        data = pd.read_csv("./data/generate/ml-1m/ratings.csv")
        hava_score_items = data[data['userId']==user]['movieId'].values
        none_score_items = set(items)-set(hava_score_items) # 集合相减，算是一种欣慰吧
        return none_score_items

    # 获取用户对item的喜好程度(3.获取用户与每个item的距离)
    def cosUI(self,user,item):
        Uia=sum(np.array(self.user_profile[str(user)])*self.item_profile[str(item)])
        Ua=math.sqrt(sum([math.pow(one,2) for one in self.user_profile[str(user)]]))
        Ia=math.sqrt(sum([math.pow(one,2) for one in self.item_profile[str(item)]]))
        return Uia/(Ua*Ia)

    # 为用户进行电影推荐
    def recommend(self,user):
        user_result = {}
        item_list = self.get_none_score_item(user)
        for item in item_list:
            user_result[item]=self.cosUI(user,item)
        if self.K is None:
            result=sorted(user_result.items(),key=lambda k:k[1],reverse=True) # 用第二个变量排序（k来自于user_result.items()的第二个值）
        else:
            result=sorted(user_result.items(),key=lambda k:k[1],reverse=True)[:self.K]
        print(result)

    def evaluate(self):
        evas=[]
        data = pd.read_csv("data/generate/ml-1m/ratings.csv")
        # 随机选取20个用户来进行效果评估
        for user in random.sample([one for one in range(1,6041)],20):
            hava_score_items=data[data['userId']==user]['movieId'].values
            items=pd.read_csv("data/generate/ml-1m/movies.csv")['movieId'].values
            user_result={}
            for item in items:
                user_result[item]=self.cosUI(user,item)
            results=sorted(user_result.items(),key=lambda k:k[1],reverse=True)[:len(hava_score_items)]
            rec_items=[]
            for one in results:
                rec_items.append(one[0])
            eva = len(set(rec_items)& set(hava_score_items))/len(hava_score_items)
            evas.append(eva)
        return  sum(evas)/len(evas)

if __name__ == '__main__':
    contentBaseRec = BaseContentRec(10)
    contentBaseRec.prepare_item_profile()
    contentBaseRec.prepare_user_profile()
    print(contentBaseRec.evaluate())